/*
Copyright (C) 2011 by Gary Wilber

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// THIS IS NOT AN OFFICAL API. I'm not affiliated with Google in any way.

// This project requires tinyxml, json, and boost to compile

// If you don't know whats going on check out http://code.google.com/p/steam-chat-relay/

#include "GoogleVoice.h"

#include "tinyxml\tinyxml.h"
#include "json\json.h"

#include "boost\regex.hpp"
#include "boost\algorithm\string\replace.hpp"
#include "boost\algorithm\string\trim.hpp"

using namespace boost;

GoogleVoice::GoogleVoice(string username, string password)
{
	loggedIn = false;

	this->username = username;
	this->password = password;

	curl = curl_easy_init();
	if(curl == NULL)
	{
		cout << "curl failed to initialize" << endl;
		return;
	}

	curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "gv_cookies.txt");
	curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "gv_cookies.txt");
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1);
	curl_easy_setopt(curl, CURLOPT_HEADER, 0);
	curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);

	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, GoogleVoice::staticCurlWriter);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curlData);

	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.854.0 Safari/535.2");
}

GoogleVoice::~GoogleVoice()
{
	cout << "Deconstructed Google Voice" << endl;
	if(curl)
	{
		curl_easy_cleanup(curl);
	}
}

bool GoogleVoice::Login()
{
	if(loggedIn)
	{
		return true;
	}

	curl_easy_setopt(curl, CURLOPT_URL, "https://accounts.google.com/accounts/ServiceLogin");

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		return false;
	}

	regex rexp; cmatch m;
	string galx;

	rexp = "name=\"GALX\"\\s*value=\"([^\"]+)\"";
	if(regex_search(curlData.c_str(), m, rexp)) 
	{ 
		galx = m[1];
		cout << "Got GALX: " << galx << endl;
	}
	else
	{
		cout << "Unable to set GALX" << endl;
		return false;
	}
	
	curl_easy_setopt(curl, CURLOPT_URL, "https://accounts.google.com/accounts/ServiceLoginAuth");
	curl_easy_setopt(curl, CURLOPT_POST, 1);
	
	string postFields = "Email=" + username + "&Passwd=" + password + "&continue=https://www.google.com/voice/account/signin" + "&GALX=" + galx;

	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str());

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		cout << "Unable to POST login details" << endl;
		return false;
	}

	if(curlData.find_first_of("https://accounts.google.com/SmsAuth?continue") != string::npos)
	{
		// We are using an authenticator
		printf("Authenticator Detected. Please enter your 6-digit verification code: ");

		string code;
		cin >> code;

		/*
		printf("%s\n", curlData.c_str());

		ofstream blah;
		blah.open("test");
		blah << curlData.c_str();
		blah.close();
		*/

		rexp = "<form id=\"verify-form\" action='(.*?)' method=\"post\"";
		if(regex_search(curlData.c_str(), m, rexp)) 
		{ 
			string action = m[1];
			//cout << "Action url: " << action << endl;

			curl_easy_setopt(curl, CURLOPT_URL, action);
			curl_easy_setopt(curl, CURLOPT_POST, 1);
	
			string postFields = "Email=" + username + "&Passwd=" + password + "&continue=https://www.google.com/voice/account/signin" + "&smsUserPin=" + code + "&smsVerifyPin=Verify&smsToken=&exp=smsauthnojs&timeStmp=&secTok=&PersistentCookie=yes&GALX=" + galx;

			curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str());

			curlData.clear();
			if(curl_easy_perform(curl) != CURLE_OK)
			{
				cout << "Unable to POST authenticator details" << endl;
				return false;
			}

			rexp = "<form action=\"(.*?)\".*?name=\"smsToken\" value=\"(.*?)\"";
			if(regex_search(curlData.c_str(), m, rexp)) 
			{ 
				action = m[1];

				curl_easy_setopt(curl, CURLOPT_URL, action);
				curl_easy_setopt(curl, CURLOPT_POST, 1);
	
				string postFields = "smsToken=" + m[2] + "&GALX=" + galx;

				curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str());

				curlData.clear();
				if(curl_easy_perform(curl) != CURLE_OK)
				{
					cout << "Unable to POST authenticator auth details" << endl;
					return false;
				}

			}
		}
	}

	rexp = "name=\"_rnr_se\".*?value=\"(.*?)\"";
	if(regex_search(curlData.c_str(), m, rexp)) 
	{ 
		rnr_se = m[1];
		loggedIn = true;
		cout << "Got rnr_se: " << rnr_se << endl;
	}

	return loggedIn;
}

bool GoogleVoice::SendSMS(string number, string message)
{
	Login();

	curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com/voice/sms/send/");
	curl_easy_setopt(curl, CURLOPT_POST, 1);

	string postFields = "_rnr_se=" + rnr_se + "&phoneNumber=+1" + number + "&text=" + message;
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields);

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		return false;
	}

	Json::Value root;
	Json::Reader reader;

	if(reader.parse(curlData, root))
	{
		if(root.get("ok", false).asBool())
		{
			return true;
		}
	}

	return false;
}

sms_vector GoogleVoice::GetNewSMS()
{
	sms_vector sms_messages;

	if(!Login())
	{
		return sms_messages;
	}

	curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com/voice/inbox/recent/sms/");
	curl_easy_setopt(curl, CURLOPT_POST, 0);

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		return sms_messages;
	}

	TiXmlDocument doc;
	doc.Parse(curlData.c_str());

	TiXmlElement *root = doc.RootElement();

	TiXmlNode *node = doc.FirstChild("response");
	if(node == NULL)
	{
		return sms_messages;
	}

	TiXmlElement *responseElement = node->ToElement();
	if(responseElement == NULL)
	{
		return sms_messages;
	}

	node = responseElement->FirstChildElement();
	if(node == NULL)
	{
		return sms_messages;
	}

	TiXmlElement *jsonElement = node->ToElement();
	if(jsonElement == NULL)
	{
		return sms_messages;
	}

	node = responseElement->LastChild();
	if(node == NULL)
	{
		return sms_messages;
	}

	TiXmlElement *htmlElement = node->ToElement();
	if(htmlElement == NULL)
	{
		return sms_messages;
	}

	Json::Value json_root;
	Json::Reader reader;

	if(!reader.parse(jsonElement->GetText(), json_root))
	{
		return sms_messages;
	}

	const Json::Value messages = json_root["messages"];
	if(messages != NULL)
	{
		const Json::Value::Members members = messages.getMemberNames();

		for(unsigned int i = 0; i < members.size(); i++)
		{
			const Json::Value message = messages[members[i]];

			bool unread = false;

			const Json::Value labels = message["labels"];

			for(unsigned int l = 0; l < labels.size(); l++)
			{
				if(labels[l].asString() == "unread")
				{
					unread = true;
				}
			}

			if(unread)
			{
				sms_data data;
				data.id = message.get("id", "").asString();
				data.message = "";
				data.phoneNumber = message.get("phoneNumber", "").asString();
				data.time = ""; //message.get("startTime", 0).asUInt();
				sms_messages.push_back(data);

				//cout << "found unread" << endl;
			}
		}
	}

	string s_html(htmlElement->GetText());
	/*
	boost::replace_all(s_html, "\n", " ");
	boost::replace_all(s_html, "\t", "");
	boost::replace_all(s_html, "\r", "");
	boost::replace_all(s_html, "  ", " ");
	*/
	regex rexp;

	//rexp = "<div id=\"(.*?)\"\n     class=\"goog-flat-button gc-message gc-message-sms\".*?<div class=\"gc-message-player\">(.*?)<td class=\"gc-message-transcript-r\"></td>";
	
	rexp = "<div id=\"(.*?)\"\n     class=\"goog-flat-button gc-message gc-message-unread gc-message-sms\">.*?<div class=\"gc-message-player\">(.*?)<td class=\"gc-message-transcript-r\"></td>";
	
	//rexp = "<div id=\"(.*?)\".*?class=\"goog-flat-button gc-message gc-message-sms\".*?<div class=\"gc-message-sms-row\">.*?<span class=\"gc-message-sms-from\">(.*?)</span>.*?<span class=\"gc-message-sms-text\">(.*?)</span>.*?<span class=\"gc-message-sms-time\">(.*?)</span>.*?</div>.*?</div>";

	string::const_iterator begin = s_html.begin();
	string::const_iterator end = s_html.end();
	match_results<string::const_iterator> what;

	//cout << "test1" << endl;

	try
	{
		//cout << rexp << endl;
		while(regex_search(begin, end, what, rexp))
		{
			string id = what[1];
			string id_msg_info = what[2];

			//cout << id << "|" << id_msg_info << endl;

			for(unsigned int i=0; i < sms_messages.size(); i++)
			{
				sms_data data = sms_messages[i];
				if(data.id == id)
				{
					cmatch m;
					regex rexp2;
					rexp2 = "<a class=\"gc-message-sms-show\" title=\"Show more messages\" href=\"javascript://\">(.*?) more messages</a>";			

					int skip = 0;

					//cout << rexp2 << endl;
					if(regex_search(id_msg_info.c_str(), m, rexp2))
					{
						//cout << "3" << endl;
						string s_skip = m[1];
						skip = atoi(s_skip.c_str());
					}
					//cout << "3" << endl;

					//cout << "skipping " << skip << endl;

					string::const_iterator begin2 = id_msg_info.begin();
					string::const_iterator end2 = id_msg_info.end();
					match_results<string::const_iterator> what2;

					rexp2 = "<div class=\"gc-message-sms-row\">.*?<span class=\"gc-message-sms-from\">(.*?)</span>.*?<span class=\"gc-message-sms-text\">(.*?)</span>.*?<span class=\"gc-message-sms-time\">(.*?)</span>.*?</div>";
				
					//cout << "found id" << endl;

					int count = 0;
					while(regex_search(begin2, end2, what2, rexp2))
					{
						if(skip == 0 || count > skip)
						{
							string phoneNumber = what2[1];
							boost::trim(phoneNumber);
							phoneNumber = phoneNumber.substr(0, phoneNumber.length() - 1);

							//cout << "'" << phoneNumber << ":" << data.phoneNumber << "'" << endl;
							if(phoneNumber == data.phoneNumber)
							{
								//cout << phoneNumber << endl;
								string message = what2[2];
								string time = what2[3];

								//cout << "Added message: " << message << endl;

								sms_messages[i].message = message;
								sms_messages[i].time = time;
							}
						}
						count++;

						begin2 = what2[0].second;
					}
					break;
				}
			}

			begin = what[0].second;
		}
	}
	catch(boost::regex_error& e)
	{
		cout << "The regexp broke!" << endl;
		throw(e);
	}

	//cout << "test2" << endl;

	return sms_messages;
}

bool GoogleVoice::MarkSMSRead(string msg_id)
{
	if(!Login())
	{
		return false;
	}

	curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com/voice/inbox/mark/");
	curl_easy_setopt(curl, CURLOPT_POST, 1);

	string postFields = "_rnr_se=" + rnr_se + "&messages=" + msg_id + "&read=1";
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields);

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		return false;
	}

	Json::Value root;
	Json::Reader reader;

	if(reader.parse(curlData, root))
	{
		if(root.get("ok", false).asBool())
		{
			return true;
		}
	}

	return false;
}

bool GoogleVoice::DeleteSMS(string msg_id)
{
	if(!Login())
	{
		return false;
	}

	curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com/voice/inbox/deleteMessages/");
	curl_easy_setopt(curl, CURLOPT_POST, 1);

	string postFields = "_rnr_se=" + rnr_se + "&trash=1&messages=" + msg_id + "&read=1";
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields);

	curlData.clear();
	if(curl_easy_perform(curl) != CURLE_OK)
	{
		return false;
	}

	Json::Value root;
	Json::Reader reader;

	if(reader.parse(curlData, root))
	{
		if(root.get("ok", false).asBool())
		{
			return true;
		}
	}

	return false;
}

int GoogleVoice::curlWriter(char *data, size_t size, size_t nmemb, string *buffer)
{
	if(buffer != NULL)
	{
		buffer->append(data, size * nmemb);
	}
	return size * nmemb;
}

int GoogleVoice::staticCurlWriter(char *data, size_t size, size_t nmemb, string *buffer, void *f)
{
	return static_cast<GoogleVoice*>(f)->curlWriter(data, size, nmemb, buffer);
}
